using System;
using System.Collections.Generic;
using System.Text;

namespace LSO
{
    static class LSO
    {
        public class InstructionInfo
        {
            public string Official;
            public string Mnemonic;
            public byte Opcode;
            public ArgType[] ArgTypes = new ArgType[0];

            public InstructionInfo(byte opcode, ArgType[] argtypes)
            {
                Opcode = opcode;
                ArgTypes = argtypes;
            }

            public InstructionInfo()
            {
            }

            public InstructionInfo(string official, string mnemonic, int opcode, ArgType[] argtypes)
            {
                Official = official;
                Mnemonic = mnemonic;
                Opcode = (byte)opcode;
                ArgTypes = argtypes;
            }

            public int NumberOfArgs
            {
                get
                {
                    return ArgTypes.Length;
                }
            }
        }

        public class Instruction
        {
            public InstructionInfo Info = new InstructionInfo();
            public List<Object> Arguments = new List<Object>();

            public int Size
            {
                get
                {
                    int size = 1;
                    int argcount = Info.ArgTypes.Length;
                    ArgType type = new ArgType();
                    for(int i = 0; i < argcount; i++)
                    {
                        type = Info.ArgTypes[i];
                        if (type == ArgType.CodeOffset || type == ArgType.Float
                            || type == ArgType.Integer || type == ArgType.LocalFrameOffset
                            || type == ArgType.StateIndex || type == ArgType.StaticFrameOffset
                            || type == ArgType.UserFunctionIndex)
                        {
                            size += 4;
                        }
                        else if (type == ArgType.LibFunctionIndexTwoByte)
                            size += 2;
                        else if (type == ArgType.LibFunctionIndexByte || type == ArgType.RType
                       || type == ArgType.Type)
                        {
                            size += 1;
                        }
                        else if (type == ArgType.String)
                        {
                            size += Arguments[i].ToString().Length + 1;
                        }
                    }
                    return size;
                }
            }

            public void FromBytes(byte[] b, int offset)
            {
                Info = LSO.InstructionByOpcode(b[offset]);
                if (Info == null)
                    throw new Exception("Invalid instruction");
                offset++;
                Arguments = new List<object>();
                int intvalue;
                float floatvalue;
                short shortvalue;
                string stringvalue;
                byte[] buffer;
                foreach (ArgType type in Info.ArgTypes)
                {
                    if (type == ArgType.CodeOffset
                        || type == ArgType.Integer || type == ArgType.LocalFrameOffset
                        || type == ArgType.StateIndex || type == ArgType.StaticFrameOffset
                        || type == ArgType.UserFunctionIndex)
                    {
                        buffer = new byte[4];
                        Buffer.BlockCopy(b, offset, buffer, 0, 4);
                        if (BitConverter.IsLittleEndian) Array.Reverse(buffer);
                        intvalue = BitConverter.ToInt32(buffer, 0);
                        Arguments.Add(intvalue);
                        offset += 4;
                    }
                    else if (type == ArgType.Float)
                    {
                        buffer = new byte[4];
                        Buffer.BlockCopy(b, offset, buffer, 0, 4);
                        if (BitConverter.IsLittleEndian) Array.Reverse(buffer);
                        floatvalue = BitConverter.ToSingle(buffer, 0);
                        Arguments.Add(floatvalue);
                        offset += 4;
                    }
                    else if (type == ArgType.LibFunctionIndexByte)
                    {
                        Arguments.Add(b[offset]);
                        offset += 2;
                    }
                    else if (type == ArgType.LibFunctionIndexTwoByte)
                    {
                        buffer = new byte[2];
                        Buffer.BlockCopy(b, offset, buffer, 0, 2);
                        if (BitConverter.IsLittleEndian) Array.Reverse(buffer);
                        shortvalue = BitConverter.ToInt16(buffer, 0);
                        Arguments.Add(shortvalue);
                        offset += 2;
                    }
                    else if (type == ArgType.LType)
                    {
                        Arguments.Add((VariableType)((b[offset] >> 4) & 0x0F));
                    }
                    else if (type == ArgType.RType)
                    {
                        Arguments.Add((VariableType)(b[offset] & 0x0F));
                        offset++;
                    }
                    else if (type == ArgType.Type)
                    {
                        Arguments.Add((VariableType)b[offset]);
                        offset++;
                    }
                    else if (type == ArgType.String)
                    {
                        stringvalue = "";
                        while (b[offset] != 0)
                        {
                            stringvalue += Encoding.ASCII.GetString(b, offset, 1);
                            offset++;
                        }
                        offset++;
                        Arguments.Add(stringvalue);
                    }
                }
            }

            public byte[] ToBytes()
            {
                byte[] b = new byte[Size];
                b[0] = Info.Opcode;
                int offset = 1;
                int argcount = Info.NumberOfArgs;
                ArgType type = new ArgType();
                byte[] buffer;
                byte ltype = 0;
                for (int i = 0; i < argcount; i++)
                {
                    type = Info.ArgTypes[i];
                    if (type == ArgType.LType)
                    {
                        ltype = Convert.ToByte(Arguments[i]);
                        ltype = (byte)(ltype << 4);
                        ltype = (byte)(ltype & 0xF0);
                    }
                    else if (type == ArgType.RType)
                    {
                        b[offset] = (byte)(ltype | (Convert.ToByte(Arguments[i]) & 0x0F));
                        offset++;
                    }
                    else if (type == ArgType.Type || type == ArgType.LibFunctionIndexByte)
                    {
                        b[offset] = Convert.ToByte(Arguments[i]);
                        offset++;
                    }
                    else if (type == ArgType.LibFunctionIndexTwoByte)
                    {
                        buffer = BitConverter.GetBytes(Convert.ToInt16(Arguments[i]));
                        if (BitConverter.IsLittleEndian) Array.Reverse(buffer);
                        Buffer.BlockCopy(buffer, 0, b, offset, buffer.Length);
                        offset += buffer.Length;
                    }
                    else if (type == ArgType.String)
                    {
                        string str = Convert.ToString(Arguments[i]);
                        buffer = new byte[str.Length + 1];
                        Buffer.BlockCopy(Encoding.ASCII.GetBytes(str), 0, b, offset, str.Length);
                        offset += buffer.Length;
                    }
                    else if (type == ArgType.Float)
                    {
                        buffer = BitConverter.GetBytes(Convert.ToSingle(Arguments[i]));
                        if (BitConverter.IsLittleEndian) Array.Reverse(buffer);
                        Buffer.BlockCopy(buffer, 0, b, offset, buffer.Length);
                        offset += buffer.Length;
                    }
                    else
                    {
                        buffer = BitConverter.GetBytes(Convert.ToInt32(Arguments[i]));
                        if (BitConverter.IsLittleEndian) Array.Reverse(buffer);
                        Buffer.BlockCopy(buffer, 0, b, offset, buffer.Length);
                        offset += buffer.Length;
                    }
                }
                return b;
            }

            public override string ToString()
            {
                string s = Info.Official;
                if (Arguments.Count > 0)
                {
                    s += " ";
                    int argcount = Info.ArgTypes.Length;
                    ArgType type = new ArgType();
                    for (int i = 0; i < argcount; i++)
                    {
                        if (i > 0) s += ", ";
                        type = Info.ArgTypes[i];
                        if (type == ArgType.String)
                        {
                            s += "\"" + Arguments[i] + "\"";
                        }
                        else
                        {
                            s += Arguments[i].ToString();
                        }
                    }
                }
                return s;
            }
        }

        public static List<InstructionInfo> InstructionInfos = new List<InstructionInfo>();

        public static InstructionInfo InstructionByName(string name)
        {
            name = name.ToUpper();
            foreach (InstructionInfo info in InstructionInfos)
            {
                if (info.Official == name)
                    return info;
            }
            return null;
        }

        public static InstructionInfo InstructionByOpcode(byte opcode)
        {
            foreach (InstructionInfo info in InstructionInfos)
            {
                if (info.Opcode == opcode)
                    return info;
            }
            return null;
        }

        static void AddInstruction(string official, string mnemonic, int opcode, ArgType[] argtypes)
        {
            InstructionInfo info = new InstructionInfo(official, mnemonic, opcode, argtypes);
            InstructionInfos.Add(info);
        }

        static LSO()
        {
            AddInstruction("NOOP", "NOP", 0x00, new ArgType[0]);
            AddInstruction("POP", "DROP_NUM", 0x01, new ArgType[0]);
            AddInstruction("POPS", "DROP_STRING", 0x02, new ArgType[0]);
            AddInstruction("POPL", "DROP_LIST", 0x03, new ArgType[0]);
            AddInstruction("POPV", "DROP_VECTOR", 0x04, new ArgType[0]);
            AddInstruction("POPQ", "DROP_ROTATION", 0x05, new ArgType[0]);
            AddInstruction("POPARG", "", 0x06, new ArgType[] { ArgType.Integer });
            AddInstruction("POPIP", "", 0x07, new ArgType[0]);
            AddInstruction("POPBP", "BUILD_CALL_FRAME", 0x08, new ArgType[0]);
            AddInstruction("POPSP", "", 0x09, new ArgType[0]);
            AddInstruction("POPSLR", "", 0x0A, new ArgType[0]);
            AddInstruction("DUP", "", 0x20, new ArgType[0]);
            AddInstruction("DUPS", "", 0x21, new ArgType[0]);
            AddInstruction("DUPL", "", 0x22, new ArgType[0]);
            AddInstruction("DUPV", "", 0x23, new ArgType[0]);
            AddInstruction("DUPQ", "", 0x24, new ArgType[0]);
            AddInstruction("STORE", "STORE_L_NUMBER", 0x30, new ArgType[] { ArgType.LocalFrameOffset });
            AddInstruction("STORES", "STORE_L_STRING", 0x31, new ArgType[] { ArgType.LocalFrameOffset });
            AddInstruction("STOREL", "STORE_L_LIST", 0x32, new ArgType[] { ArgType.LocalFrameOffset });
            AddInstruction("STOREV", "STORE_L_VECTOR", 0x33, new ArgType[] { ArgType.LocalFrameOffset });
            AddInstruction("STOREQ", "STORE_L_ROTATION", 0x34, new ArgType[] { ArgType.LocalFrameOffset });
            AddInstruction("STOREG", "STORE_G_NUMBER", 0x35, new ArgType[] { ArgType.StaticFrameOffset });
            AddInstruction("STOREGS", "STORE_G_STRING", 0x36, new ArgType[] { ArgType.StaticFrameOffset });
            AddInstruction("STOREGL", "STORE_G_LIST", 0x37, new ArgType[] { ArgType.StaticFrameOffset });
            AddInstruction("STOREGV", "STORE_G_VECTOR", 0x38, new ArgType[] { ArgType.StaticFrameOffset });
            AddInstruction("STOREGQ", "STORE_G_ROTATION", 0x39, new ArgType[] { ArgType.StaticFrameOffset });
            AddInstruction("LOADP", "POP_L_NUMBER", 0x3A, new ArgType[] { ArgType.LocalFrameOffset });
            AddInstruction("LOADSP", "POP_L_STRING", 0x3B, new ArgType[] { ArgType.LocalFrameOffset });
            AddInstruction("LOADLP", "POP_L_LIST", 0x3C, new ArgType[] { ArgType.LocalFrameOffset });
            AddInstruction("LOADVP", "POP_L_VECTOR", 0x3D, new ArgType[] { ArgType.LocalFrameOffset });
            AddInstruction("LOADQP", "POP_L_ROTATION", 0x3E, new ArgType[] { ArgType.LocalFrameOffset });
            AddInstruction("LOADGP", "POP_G_NUMBER", 0x3F, new ArgType[] { ArgType.StaticFrameOffset });
            AddInstruction("LOADGSP", "POP_G_STRING", 0x40, new ArgType[] { ArgType.StaticFrameOffset });
            AddInstruction("LOADGLP", "POP_G_LIST", 0x41, new ArgType[] { ArgType.StaticFrameOffset });
            AddInstruction("LOADGVP", "POP_G_VECTOR", 0x42, new ArgType[] { ArgType.StaticFrameOffset });
            AddInstruction("LOADGQP", "POP_G_ROTATION", 0x43, new ArgType[] { ArgType.StaticFrameOffset });
            AddInstruction("PUSH", "PUSH_L_NUMBER", 0x50, new ArgType[] { ArgType.LocalFrameOffset });
            AddInstruction("PUSHS", "PUSH_L_STRING", 0x51, new ArgType[] { ArgType.LocalFrameOffset });
            AddInstruction("PUSHL", "PUSH_L_LIST", 0x52, new ArgType[] { ArgType.LocalFrameOffset });
            AddInstruction("PUSHV", "PUSH_L_VECTOR", 0x53, new ArgType[] { ArgType.LocalFrameOffset });
            AddInstruction("PUSHQ", "PUSH_L_ROTATION", 0x54, new ArgType[] { ArgType.LocalFrameOffset });
            AddInstruction("PUSHG", "PUSH_G_NUMBER", 0x55, new ArgType[] { ArgType.StaticFrameOffset });
            AddInstruction("PUSHGS", "PUSH_G_STRING", 0x56, new ArgType[] { ArgType.StaticFrameOffset });
            AddInstruction("PUSHGL", "PUSH_G_LIST", 0x57, new ArgType[] { ArgType.StaticFrameOffset });
            AddInstruction("PUSHGV", "PUSH_G_VECTOR", 0x58, new ArgType[] { ArgType.StaticFrameOffset });
            AddInstruction("PUSHGQ", "PUSH_G_ROTATION", 0x59, new ArgType[] { ArgType.StaticFrameOffset });
            AddInstruction("PUSHIP", "", 0x5A, new ArgType[0]);
            AddInstruction("PUSHBP", "PUSH_ARGS_START", 0x5B, new ArgType[0]);
            AddInstruction("PUSHSP", "PUSH_ARGS_END", 0x5C, new ArgType[0]);
            AddInstruction("PUSHARGB", "LIST_BUILD", 0x5D, new ArgType[] { ArgType.Type });
            AddInstruction("PUSHARGI", "PUSH_INTEGER", 0x5E, new ArgType[] { ArgType.Integer });
            AddInstruction("PUSHARGF", "PUSH_FLOAT", 0x5F, new ArgType[] { ArgType.Float });
            AddInstruction("PUSHARGS", "PUSH_STRING", 0x60, new ArgType[] { ArgType.String });
            AddInstruction("PUSHARGV", "PUSH_VECTOR", 0x61, new ArgType[] { ArgType.Float, ArgType.Float, ArgType.Float });
            AddInstruction("PUSHARGQ", "PUSH_ROTATION", 0x62, new ArgType[] { ArgType.Float, ArgType.Float, ArgType.Float, ArgType.Float });
            AddInstruction("PUSHE", "PUSH_VOID4", 0x63, new ArgType[0]);
            AddInstruction("PUSHEV", "PUSH_VOID12", 0x64, new ArgType[0]);
            AddInstruction("PUSHEQ", "PUSH_VOID16", 0x65, new ArgType[0]);
            AddInstruction("PUSHARGE", "PUSH_LOCAL_ALLOC", 0x66, new ArgType[] { ArgType.Integer });
            AddInstruction("ADD", "MATH_ADD", 0x70, new ArgType[] { ArgType.LType, ArgType.RType });
            AddInstruction("SUB", "MATH_SUBTRACT", 0x71, new ArgType[] { ArgType.LType, ArgType.RType });
            AddInstruction("MUL", "MATH_MULTIPLY", 0x72, new ArgType[] { ArgType.LType, ArgType.RType });
            AddInstruction("DIV", "MATH_DIVIDE", 0x73, new ArgType[] { ArgType.LType, ArgType.RType });
            AddInstruction("MOD", "MATH_MODULO", 0x74, new ArgType[] { ArgType.LType, ArgType.RType });
            AddInstruction("EQ", "COMPARE_EQ", 0x75, new ArgType[] { ArgType.LType, ArgType.RType });
            AddInstruction("NEQ", "COMPARE_NE", 0x76, new ArgType[] { ArgType.LType, ArgType.RType });
            AddInstruction("LEQ", "COMPARE_LTE", 0x77, new ArgType[] { ArgType.LType, ArgType.RType });
            AddInstruction("GEQ", "COMPARE_GTE", 0x78, new ArgType[] { ArgType.LType, ArgType.RType });
            AddInstruction("LESS", "COMPARE_LT", 0x79, new ArgType[] { ArgType.LType, ArgType.RType });
            AddInstruction("GREATER", "COMPARE_GT", 0x7A, new ArgType[] { ArgType.LType, ArgType.RType });
            AddInstruction("BITAND", "BINARY_AND", 0x7B, new ArgType[0]);
            AddInstruction("BITOR", "BINARY_OR", 0x7C, new ArgType[0]);
            AddInstruction("BITXOR", "BINARY_XOR", 0x7D, new ArgType[0]);
            AddInstruction("BOOLAND", "LOGICAL_AND", 0x7E, new ArgType[0]);
            AddInstruction("BOOLOR", "LOGICAL_OR", 0x7F, new ArgType[0]);
            AddInstruction("NEG", "MATH_NEGATIVE", 0x80, new ArgType[] { ArgType.Type });
            AddInstruction("BITNOT", "COMPLEMENT", 0x81, new ArgType[0]);
            AddInstruction("BOOLNOT", "LOGICAL_NOT", 0x82, new ArgType[0]);
            AddInstruction("JUMP", "BRANCH", 0x90, new ArgType[] { ArgType.CodeOffset });
            AddInstruction("JUMPIF", "BRANCH_TRUE", 0x91, new ArgType[] { ArgType.Type, ArgType.CodeOffset });
            AddInstruction("JUMPNIF", "BRANCH_FALSE", 0x92, new ArgType[] { ArgType.Type, ArgType.CodeOffset });
            AddInstruction("STATE", "", 0x93, new ArgType[] { ArgType.StateIndex });
            AddInstruction("CALL", "CALL_FUNCTION", 0x94, new ArgType[] { ArgType.UserFunctionIndex });
            AddInstruction("RETURN", "", 0x95, new ArgType[0]);
            AddInstruction("CAST", "CAST_TYPE", 0xA0, new ArgType[] { ArgType.LType, ArgType.RType });
            AddInstruction("STACKTOS", "", 0xB0, new ArgType[] { ArgType.Integer });
            AddInstruction("STACKTOL", "MERGE_LIST", 0xB1, new ArgType[] { ArgType.Integer });
            AddInstruction("PRINT", "", 0xC0, new ArgType[] { ArgType.Type });
            AddInstruction("CALLLIB", "", 0xD0, new ArgType[] { ArgType.LibFunctionIndexByte });
            AddInstruction("CALLLIB_TWO_BYTE", "CALL_BUILTIN", 0xD1, new ArgType[] { ArgType.LibFunctionIndexTwoByte });
            AddInstruction("SHL", "BINARY_SHL", 0xE0, new ArgType[0]);
            AddInstruction("SHR", "BINARY_SHR", 0xE1, new ArgType[0]);
        }
    }
}
